using Red.CommUtils.Application;
using Red.Controls.Shell;
using Red.Core;
using Red.Core.Extensions;
using Red.Core.Network;
using Red.Core.Network.PacketNetwork;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Diagnostics;
using P4 = Red.Core.Perforce;

namespace Red.Toolkit.Host
{
    /// <summary>
    ///     Represents the hosting application.
    /// </summary>
    public partial class HostApp : RedApplication, IEditorHost
    {
        #region Data

        private readonly EditorManager _editorManager;
        private EditorSharedData _sharedData;
        private IResourceBrowser _mainResourceBrowser;
        private static string _defaultEditorName = "Red.Toolkit.AssetBrowser";
        private static string _cmdLineFile = "cmdline.txt";

        #endregion Data

        #region C-tor

        HostApp()
        {
            _editorManager = new EditorManager( this );
            // Set the invariant culture for the main UI thread, mainly to solve string <-> float conversion issues
            Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
            Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
        }

        #endregion C-tor

        #region IHostApp implementation

        public async Task<bool> LaunchEditorAsync( string editorName, string instanceContext, CommandLineDictionary additionalParams, Window msgParent = null )
        {
            string dllName = editorName + ".dll";
            var launchResult = await _editorManager.LaunchEditorAsync( dllName, instanceContext, additionalParams, EditorKind.Custom, _sharedData );
            if ( launchResult )
            {
                if ( _mainResourceBrowser == null && launchResult.Value is IResourceBrowser )
                { // first resource browser launched - set it as the default
                    _mainResourceBrowser = (IResourceBrowser)launchResult.Value;
                    launchResult.Value.Closed += ( s, e ) => _mainResourceBrowser = null;
                }
            }
            else
            {
                await RedMessageBox.ShowAsync( msgParent, RedDialogType.Ok, "Unable to start editor\n\n" + launchResult.Message, "Error" );
                return false;
            }
            return true;
        }

        public async Task<bool> EditResourceAsync( string path, Window msgParent = null )
        {
            var launchResult = await _editorManager.EditResourceAsync( path, _sharedData );
            if ( !launchResult )
            {
                await RedMessageBox.ShowAsync( msgParent, RedDialogType.Ok, "Unable to edit resource\n\n" + launchResult.Message, "Error" );
                return false;
            }
            return true;
        }

        public async Task<bool> ShowResourceAsync( string path )
        {
            await LaunchMainEditor( new CommandLineDictionary() );

            if ( _mainResourceBrowser != null )
            {
                _mainResourceBrowser.NavigateToResource( path );
                return true;
            }

            return false;
        }

        #endregion IHostApp implementation

        #region Private methods

        private CommandLineDictionary GetCommandLine( string[] args )
        {
            string[] fileArgs = new string[0];
            CommandLineDictionary result = null;

            if ( File.Exists( _cmdLineFile ) )
            {
                try
                {
                    using ( StreamReader sr = new StreamReader( _cmdLineFile ) )
                    {
                        string content = sr.ReadToEnd();
                        fileArgs = Regex.Split( content, @"\s" ).Where( s => !string.IsNullOrEmpty( s ) ).ToArray();
                        var cmdFileParams = CommandLineDictionary.FromArguments( fileArgs );
                    }
                }
                catch ( SystemException e )
                {
                    RedMessageBox.Show( null, RedDialogType.Ok, e.Message, "Error" );
                }
            }

            try
            {
                result = CommandLineDictionary.FromArguments( fileArgs );

                foreach ( var cmd in CommandLineDictionary.FromArguments( args ) )
                { // directly given arguments override those from the file
                    result[ cmd.Key ] = cmd.Value;
                }
            }
            catch ( ArgumentException e )
            {
                RedMessageBox.Show( null, RedDialogType.Ok, e.Message, "Error" );
            }

            return result;
        }

        private DepotInfo InitDepot( CommandLineDictionary cmdParams )
        {
            string p4Server = "perforce:1666", p4User = String.Empty, p4Workspace = String.Empty;
            string rootStr = String.Empty, roRootStr = String.Empty;

            foreach ( var cmdParam in cmdParams )
            {
                switch ( cmdParam.Key )
                {
                    case "p4server": p4Server = cmdParam.Value; break;
                    case "p4user": p4User = cmdParam.Value; break;
                    case "p4workspace": p4Workspace = cmdParam.Value; break;
                    case "root": rootStr = cmdParam.Value; break;
                    case "ro_root": roRootStr = cmdParam.Value; break;
                }
            }

            string[] roots = rootStr.Split( new[] { ';' } );
            string[] roRoots = roRootStr.Split( new[] { ';' } );

            var numEntryPoints = roots.Length + roRoots.Length;
            var entryPoints = new DepotInfo.EntryPoint[ numEntryPoints ];

            for ( int i = 0; i < roots.Length; ++i )
                entryPoints[ i ] = new DepotInfo.EntryPoint( roots[ i ], false );

            for ( int i = 0; i < roRoots.Length; ++i )
                entryPoints[ i + roots.Length ] = new DepotInfo.EntryPoint( roRoots[ i ], true, @"engine" );

            var sourceControl = new P4.Mediator();

            // Connect to p4mediator event directly, as it's only temporary and we need an immediate response if it's invoked
            sourceControl.Error += SourceControlInitError;

            if ( !sourceControl.Initialize( new P4.Context( p4Server, p4User, p4Workspace ) ) )
                return null;

            // Source control initialisation successful - let the apps take over from here
            sourceControl.Error -= SourceControlInitError;

            var rootPath = sourceControl.GetDepotRoot();
            string normalizedRootPath = string.Empty;
            if ( rootPath != null )
            {
                normalizedRootPath = NormalizePath( rootPath );
            }

            return new DepotInfo( normalizedRootPath, sourceControl, entryPoints );
        }

        private void SourceControlInitError( string message )
        {
            RedMessageBox.Show( null, RedDialogType.Ok, message, "Error connecting to Source Control" );
        }

        private string NormalizePath( string path )
        {
            var normalizedPath = path.Replace( '/', '\\' );
            var directoryInfo = new DirectoryInfo( normalizedPath );
            if ( directoryInfo.Parent != null )
            {
                normalizedPath = normalizedPath.TrimEnd( new[] { '\\' } );
            }

            return normalizedPath;
        }

        private async Task<bool> LaunchMainEditor( CommandLineDictionary cmdParams )
        {
            string editorDllName = _defaultEditorName;
            string instanceContext = "Default";
            var additionalParams = new CommandLineDictionary();

            foreach ( var cmdParam in cmdParams )
            {
                switch ( cmdParam.Key )
                {
                    case "editor": editorDllName = cmdParam.Value; break;
                    case "context": instanceContext = cmdParam.Value; break;
                    default: // pass unknown params further
                        additionalParams.Add( cmdParam.Key, cmdParam.Value );
                        break;
                }
            }

            return await LaunchEditorAsync( editorDllName, instanceContext, additionalParams );
        }

        private string GetGameDataPath( CommandLineDictionary cmdParams )
        {
            string gameDataPath;
            cmdParams.TryGetValue( "gd_root", out gameDataPath );
            return gameDataPath;
        }

        private string GetEnginePath( CommandLineDictionary cmdParams )
        {
            string gameDataPath;
            cmdParams.TryGetValue( "ro_root", out gameDataPath );
            return gameDataPath;
        }

        private string GetRootGamePath( DepotInfo depotInfo, CommandLineDictionary cmdParams )
        {
            string gamePrefix;
            if( !cmdParams.TryGetValue( "gamePrefix", out gamePrefix ) )
            {
                gamePrefix = "r6";
            }

            return Path.Combine( depotInfo.RootPath, gamePrefix );
        }
        
        private static string FindLocalEngineAppExePath( string engineAppExePath )
        {
            if( Path.IsPathRooted( engineAppExePath ) )
            {
                if( File.Exists( engineAppExePath ) )
                {
                    return engineAppExePath;
                }
            }

            string currentDir = Environment.CurrentDirectory;
            string fullPath = null;
            while ( currentDir != null )
            {
                fullPath = Path.Combine( currentDir, engineAppExePath );
                if ( File.Exists( fullPath ) )
                {
                    return fullPath;
                }
                var dir = Directory.GetParent( currentDir );
                currentDir = dir != null ? dir.ToString() : null;
            }

            return null; // not found
        }

        private IAppLauncher InitEngineLauncher( CommandLineDictionary cmdParams )
        {
            // Filled based on command line arguments
            IPAddress engineAppAddress = IPAddress.Loopback;

            string engineAppPath = null;
            foreach ( var cmdParam in cmdParams )
            {
                switch ( cmdParam.Key )
                {
                    case "engineAppExePath":
                        {
                            engineAppPath = cmdParam.Value;
                            engineAppPath = FindLocalEngineAppExePath( engineAppPath );
                            break;
                        }
                }
            }

            if ( engineAppPath == null )
                engineAppPath = GetNewestLauncherPath();

            if ( engineAppPath == null )
            {
                return null;
            }

            Action< Exception > onNetworkError = ( Exception ex ) =>
            {
                Dispatcher.BeginInvoke( (Action)delegate
                {
                    RedMessageBox.Show( null, RedDialogType.Ok, ex.Message, "Error" );
                } );
            };

            PacketNetworkManager network = new PacketNetworkManager( new RawTcpManager( onNetworkError ) );
            ChannelFactory channelFactory = new ChannelFactory( network );

            IAppLauncher localEngineLauncher = new AppLauncherLocalPC( channelFactory, engineAppPath );

            return localEngineLauncher;
        }

        private string GetNewestLauncherPath()
        {
            string curDir = Directory.GetParent( Environment.CurrentDirectory ).FullName;
            while ( Path.GetFileName( curDir ) != "bin" )
            {
                var parentDir = Directory.GetParent( curDir );
                if ( parentDir == null )
                    break;

                curDir = parentDir.FullName;
            }

            string[] launcherFiles = Directory.GetFiles( curDir, "launcher.exe", SearchOption.AllDirectories );

            DateTime newerDateTime = new DateTime();
            string launcherPath = null;

            for ( int i = 0; i < launcherFiles.Length; ++i )
            {
                var lastWriteTime = File.GetLastWriteTime( launcherFiles[i] );
                if ( lastWriteTime > newerDateTime )
                {
                    newerDateTime = lastWriteTime;
                    launcherPath = launcherFiles[i];
                }
            }

            // check how old the file is
            if ( launcherPath != null )
            {
                const int maxDays = 2;
                var deltaTime = DateTime.Now - File.GetLastWriteTime( launcherPath );
                if ( deltaTime.Days > maxDays )
                {
                    var text = string.Format( $"Launcher executable file is older than {maxDays} days.\nDo you want to use it with toolkit ?" );
                    if ( !RedMessageBox.Show( null, RedDialogType.YesNo, text, "Warning" ) )
                        launcherPath = null;
                }
            }

            return launcherPath;
        }

        private void InitTheme( CommandLineDictionary cmdParams )
        {
            string accent;
            if ( cmdParams.TryGetValue( "theme-accent", out accent ) )
            {
                ChangeAccentColor( accent );
            }

            string theme;
            if ( cmdParams.TryGetValue( "theme", out theme ) )
            {
                ChangeAppTheme( theme );
            }
        }

        #endregion Private methods

        #region Event handlers

        private async void Application_Startup( object sender, StartupEventArgs e )
        {
            CommandLineDictionary cmdParams = GetCommandLine( e.Args );
            if ( cmdParams == null )
            {
                Shutdown();
                return;
            }

            if ( cmdParams.ContainsKey( "debug" ) && !Debugger.IsAttached )
            {
                Debugger.Launch();
            }

            InitTheme( cmdParams );

            DepotInfo depotInfo = InitDepot( cmdParams );
            if ( depotInfo == null )
            {
                Shutdown();
                return;
            }

            IAppLauncher engLauncher = InitEngineLauncher( cmdParams );
            if ( engLauncher == null )
            {
                RedMessageBox.Show( null, RedDialogType.Ok, "Can't find engine application executable path.\nMake sure you have proper binaries downloaded.", "Error" );
                Shutdown();
                return;
            }

            var gameDataPath = GetGameDataPath( cmdParams );
            if ( string.IsNullOrEmpty( gameDataPath ) )
            {
                RedMessageBox.Show( null, RedDialogType.Ok, "Can't find GameData root path (gd_root).\nMake sure you have proper path setup.", "Error" );
                Shutdown();
                return;
            }

            var enginePath = GetEnginePath( cmdParams );
            if ( string.IsNullOrEmpty( enginePath ) )
            {
                RedMessageBox.Show( null, RedDialogType.Ok, "Can't find engine root path (ro_root).\nMake sure you have proper path setup.", "Error" );
                Shutdown();
                return;
            }

            var gameRootPath = GetRootGamePath( depotInfo, cmdParams );
            
            _sharedData = new EditorSharedData( depotInfo, engLauncher, gameDataPath, enginePath, gameRootPath );

            _sharedData.InitializationError += SharedData_InitializationError;

            if ( cmdParams.ContainsKey( "preloadEngine" ) )
            {
                _sharedData.InitEngineDataAsync().DoNotAwait();
            }
            
            var editorLaunched = await LaunchMainEditor( cmdParams );
            if ( !editorLaunched )
            { // if the main editor launching failed, there is no point in continue
                Shutdown();
                return;
            }
        }

        private void Application_Exit( object sender, ExitEventArgs e )
        {
            _sharedData?.EngineApp?.RequestClose();
        }

        private async void SharedData_InitializationError( Exception ex )
        {
            await RedMessageBox.ShowAsync(
                null, RedDialogType.Ok, "Error initializing editor shared data\n\n" + ex.Message, "Error"
                );
        }       
        
        #endregion Event handlers
    }
}
